package com.jzo2o.market.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONArray;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.mapper.CouponWriteOffMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.domain.CouponWriteOff;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.CountResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageHelperUtils;
import com.jzo2o.mysql.utils.PageUtils;
import net.bytebuddy.implementation.bytecode.Throw;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
import static com.jzo2o.market.enums.ActivityStatusEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponService couponService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    @Autowired
    private CouponMapper couponMapper;

    @Autowired
    private CouponWriteOffMapper couponWriteOffMapper;


    //根据页面状态查询活动 todo 非常不熟悉，每日练习
    @Override
    public List<SeizeCouponInfoResDTO> queryForPage(Integer tabType) {
        //如果状态为疯抢中，查询正在进行中的活动返回，如果为未开始，且时间不足一个月，返回未进行的活动--要在redis中查询

        //1，先查询redis，获得预热数据
        String sizeActivityListStr = (String)redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if(ObjectUtils.isNull(sizeActivityListStr)){
            return List.of();
        }
        //预热中的活动本就是不足一月的活动
        List<SeizeCouponInfoResDTO> seizeCouponInfoList = JSON.parseArray(sizeActivityListStr, SeizeCouponInfoResDTO.class);

        //筛选
        List<SeizeCouponInfoResDTO> resultList = seizeCouponInfoList.stream().filter(e -> {
            //先拿到真实状态-是否展示在疯抢中或者进行中需要在下面判断
            int realStatus = getStatus(e.getDistributeStartTime(), e.getDistributeEndTime(), e.getStatus());
            if (tabType == TabTypeConstants.SEIZING) {
                //筛选进行中的活动,返回状态为进行中的活动
                return realStatus == DISTRIBUTING.getStatus();
            } else {
                //返回未开始状态的活动-预热中的活动本就是不足一月的活动，不需要再次筛选
                return realStatus == NO_DISTRIBUTE.getStatus();
            }
        }).map(e -> {
            //剩余库存数量为
            e.setRemainNum(e.getStockNum());
            return e;
        }).collect(Collectors.toList());


        return resultList;
    }

    //配合-根据页面状态查询活动-接口
    /**
     * 根据活动的目前状态、开始、结束时间 对比当前时间来获取到活动的真实状态
     * 1. 状态在待生效, 但是 活动开始时间 <=当前时间 < 活动结束时间  真实状态应该是 进行中
     * 2. 状态在待生效, 但是 活动结束时间 < 当前时间               真实状态应该是 已结束
     * 3. 状态在进行中, 但是 活动结束时间 < 当前时间               真实状态应该是 已结束
     * 4. 其它情况, 当前状态就是真实状态
     *
     * @param distributeStartTime 活动开始时间
     * @param distributeEndTime   活动结束时间
     * @param status              当前状态
     * @return 活动的真实状态
     */
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        if (NO_DISTRIBUTE.getStatus() == status && distributeStartTime.isBefore(DateUtils.now()) && distributeEndTime.isAfter(LocalDateTime.now())) {
            //状态在待生效, 但是 活动开始时间<=当前时间<活动结束时间  真实状态应该是 进行中
            return DISTRIBUTING.getStatus();
        } else if (NO_DISTRIBUTE.getStatus() == status && distributeEndTime.isBefore(LocalDateTime.now())) {
            //状态在待生效, 但是 活动结束时间 < 当前时间   真实状态应该是 已结束
            return LOSE_EFFICACY.getStatus();
        } else if (DISTRIBUTING.getStatus() == status && distributeEndTime.isBefore(LocalDateTime.now())) {
            //状态在进行中, 但是 活动结束时间 < 当前时间   真实状态应该是 已结束
            return LOSE_EFFICACY.getStatus();
        }
        return status;
    }


    @Override
    public void updateStatus() {

        //对于待生效的活动到达发放开始时间状态改变为进行中
        boolean isUpdate1 = this.lambdaUpdate()
                .eq(Activity::getStatus, NO_DISTRIBUTE.getStatus())
                .le(Activity::getDistributeStartTime, LocalDateTime.now())
                .gt(Activity::getDistributeEndTime, LocalDateTime.now())
                .set(Activity::getStatus, DISTRIBUTING.getStatus())
                .update();
        if(!isUpdate1){
            throw new BadRequestException("更新失败");
        }
        //对于待生效及进行中的活动到达发放时间状态改变为已失效
        boolean isUpdate2 = this.lambdaUpdate()
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))
                .lt(Activity::getDistributeEndTime, LocalDateTime.now())
                .set(Activity::getStatus, LOSE_EFFICACY.getStatus())
                .update();
        if(!isUpdate2){
            throw new BadRequestException("更新失败");
        }
    }

    //定时任务-活动预热 todo 抢券活动
    @Override
    public void preHeat() {
        //1,查询活动状态为进行中或者待开时，但是当前时间距离开始时间不足一个月的活动
        List<Activity> activityHeatList = this.lambdaQuery()
                .in(Activity::getStatus, DISTRIBUTING.getStatus(), NO_DISTRIBUTE.getStatus())
                .lt(Activity::getDistributeStartTime, LocalDateTime.now().plusMonths(1))
                .list();
           //防止缓存穿透
        if(activityHeatList.isEmpty()){
            activityHeatList=List.of();
        }
        //2,将查询结果转换为json字符串
        List<SeizeCouponInfoResDTO> seizeCouponInfoList = BeanUtils.copyToList(activityHeatList, SeizeCouponInfoResDTO.class);
        String seizeCouponInfoListJsonStr = JsonUtils.toJsonStr(seizeCouponInfoList);
        //3,保存到redis中
        redisTemplate.opsForValue().set(ACTIVITY_CACHE_LIST, seizeCouponInfoListJsonStr);

        //todo 亮点
        //抢券预热活动，保存到redis中
           //如果是待生效的活动，直接保存
        seizeCouponInfoList.stream().filter(
                e->getStatus(e.getDistributeStartTime(),e.getDistributeEndTime(),e.getStatus())== NO_DISTRIBUTE.getStatus()
        ).forEach(e->{
            redisTemplate.opsForHash().put(String.format(COUPON_RESOURCE_STOCK, e.getId() % 10),e.getId(),e.getStockNum());
        });
           //如果是已生效的活动，已经保存过的不再保存
        seizeCouponInfoList.stream().filter(
                e->getStatus(e.getDistributeStartTime(),e.getDistributeEndTime(),e.getStatus())== DISTRIBUTING.getStatus()
        ).forEach(e->{
            redisTemplate.opsForHash().putIfAbsent(String.format(COUPON_RESOURCE_STOCK, e.getId() % 10),e.getId(),e.getStockNum());
        });
    }




    //新增优惠券活动
    @Override
    public void saveOrUpdateById(ActivitySaveReqDTO activitySaveReqDTO) {
        //思路：新增优惠价活动，涉及活动表一张表
        //1,先判断是新增还是修改操作
        Long id = activitySaveReqDTO.getId();
        Activity activity = new Activity();
        BeanUtil.copyProperties(activitySaveReqDTO, activity);
        if(ObjectUtils.isNull(id)){
            //说明是新增操作，填充剩余字段，新增状态只能是待生效
            activity.setStatus(ActivityStatusEnum.NO_DISTRIBUTE.getStatus());
            activity.setStockNum(activity.getTotalNum());
            this.save( activity);
        }
        else if(ObjectUtils.isNotNull( id)){
            //说明是修改操作
            this.updateById(activity);
        }
    }


    //优惠券分页查询 todo 非常重要！！！！
    @Override
    public PageResult<ActivityInfoResDTO> findByPage(ActivityQueryForPageReqDTO dto) {
        //1，处理分页参数 todo 复习mybatis plus分页查询
        Page<Activity> pageList = PageUtils.parsePageQuery(dto, Activity.class);
        //2，处理业务参数
        LambdaQueryWrapper<Activity> queryWrapper = new LambdaQueryWrapper<Activity>()
                .eq(dto.getId()!=null,Activity::getId, dto.getId())
                .like(ObjectUtils.isNotNull(dto.getName()),Activity::getName, dto.getName())
                .eq(dto.getType()!=null,Activity::getType, dto.getType())
                .eq(dto.getStatus()!=null,Activity::getStatus, dto.getStatus());
        //3，执行分页方法
        pageList = this.page(pageList, queryWrapper);

        //4，优化，将两个参数提前查询准备
            //4.1,优惠券数量查询
            //4.1.1 拿到活动id集合
        List<Long> activityIds = pageList.getRecords().stream().map(Activity::getId).collect(Collectors.toList());
            //4.1.2 执行优惠劵sql查询优惠券数量
        List<CountResDTO> couponCountList =couponMapper.findByActivityId(activityIds);
        Map<Long, Integer> couponCountMap = couponCountList.stream().collect(Collectors.toMap(CountResDTO::getActivityId, CountResDTO::getNum));

           //4.2,核销数量数量查询
           //4，2.1 拿到活动数量id，上面已完成
           //4.2.2 执行优惠券核销sql查询
        List<CountResDTO> couponWriteOffCountList=couponWriteOffMapper.findByActivityId(activityIds);
        Map<Long, Integer> couponWriteOffCountMap = couponWriteOffCountList.stream().collect(Collectors.toMap(CountResDTO::getActivityId, CountResDTO::getNum));

        //5，处理返回参数
        if(CollUtils.isEmpty(pageList.getRecords())){
            //执行结果为空，返回空集合
            return new PageResult<ActivityInfoResDTO>(pageList.getTotal(),pageList.getPages(),List.of());
        }
            //5.1 返回结果不为空，封装返回数据
        List<ActivityInfoResDTO> resultList = pageList.getRecords().stream().map(item -> {
            ActivityInfoResDTO activityInfoResDTO = new ActivityInfoResDTO();
            BeanUtils.copyProperties(item, activityInfoResDTO);
            //封装剩余两个参数
            //5.2根据活动id查询优惠券发放数量以及核销数量
            activityInfoResDTO.setReceiveNum(couponCountMap.getOrDefault(item.getId(),0));
            activityInfoResDTO.setWriteOffNum(couponWriteOffCountMap.getOrDefault(item.getId(),0));
            return activityInfoResDTO;
        }).collect(Collectors.toList());

        return new PageResult<ActivityInfoResDTO>(pageList.getPages(), pageList.getTotal(), resultList);
    }


    //查询活动详情
    @Override
    public ActivityInfoResDTO findByActivityId(Long id) {
        Activity activity = this.getById(id);
        if(ObjectUtils.isNull(activity)){
           throw new ForbiddenOperationException("活动不存在");
        }
        ActivityInfoResDTO activityInfoResDTO = BeanUtils.toBean(activity, ActivityInfoResDTO.class);
        //查询优惠劵数量
        Integer count1 = couponService.lambdaQuery().eq(Coupon::getActivityId, id).count();
        //查询活动核销数量
        Integer count2 = couponWriteOffService.lambdaQuery().eq(CouponWriteOff::getActivityId, id).count();
        activityInfoResDTO.setReceiveNum(count1);
        activityInfoResDTO.setWriteOffNum(count2);
        return activityInfoResDTO;
    }


    //撤销活动
    @Override
    @Transactional(rollbackFor  = Exception.class)
    public void revoke(Long id) {
        //1,根据id查询活动，查看状态
        Activity activity = this.getById(id);
        if(ObjectUtils.isNull(activity)){
            throw new ForbiddenOperationException("活动不存在");
        }
        //2,修改活动表状态
          //2.1判断活动状态
        if(activity.getStatus()!=1&&activity.getStatus()!=2){
            throw new ForbiddenOperationException("活动状态异常");
        }
        boolean updateActivity = this.lambdaUpdate()
                .in(Activity::getStatus, 1, 2)
                .eq(Activity::getId, id)
                .set(Activity::getStatus, 4)
                .update();
        if(!updateActivity){
            throw new ForbiddenOperationException("活动状态更新失败");
        }
        //修改优惠券表状态为已作废
        boolean updateCoupon = couponService.lambdaUpdate()
                .eq(Coupon::getActivityId, id)
                .eq(Coupon::getStatus, 1)
                .set(Coupon::getStatus, 4)
                .update();
        if(!updateCoupon){
            throw new ForbiddenOperationException("优惠券状态更新失败");
        }
    }



    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        //1. 缓存中获取活动信息
        String jsonString = (String) redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (StringUtils.isEmpty(jsonString)) {
            return null;
        }

        //2. 字符串转换为集合
        List<ActivityInfoResDTO> activityInfoResDTOList = JSON.parseArray(jsonString, ActivityInfoResDTO.class);
        if (CollUtil.isEmpty(activityInfoResDTOList)) {
            return null;
        }

        //3. 过滤出指定id的活动
        return activityInfoResDTOList.stream()
                .filter(e -> e.getId().equals(id))
                .findFirst().orElse(null);
    }
}
